Программирование и научные вычисления на языке Python/§17
Случайные числа находят множество применений в науке и программировании. Цель этого урока в том, чтобы рассмотреть некоторые практические задачи случайных чисел и научиться писать программы, с ними работающие. Для генерации случайных чисел в Python имеется специальный модуль random. Функция random.random() генерирует возвращает случайные числа в интервале 1). Попробуем: >>> import random >>> random.random() 0.33643236965993584 >>> random.random() 0.53110615544542961 >>> random.random() 0.80435595999923271 Получение случайных чисел основано на специальных алгоритмах, то есть они не являются действительно случайными, однако, таковыми в силу их близких свойств, мы можем считать. Seed - задает случайность Каждый раз, когда мы используем функцию random.random(), мы на выходе получаем различные числа. Это ясно и естественно, но может быть не очень удобным, когда мы ищем ошибку и тогда удобнее получать один и тот же расклад для каждого запуска программы. Для этого имеется функция random.seed(), вызываемая сразу же перед началом генерации чисел: >>> random.seed(2) >>> ['%.2f' % random.random() for i in range(7) '0.95', '0.06', '0.08', '0.84', '0.74', '0.67' >>> % random.random() for i in range(7) '0.61', '0.61', '0.58', '0.16', '0.43', '0.39' А теперь, если random.seed() снова передать то же число: >>> random.seed(2) >>> % random.random() for i in range(7) '0.95', '0.06', '0.08', '0.84', '0.74', '0.67' Если мы не устанавливаем seed сами, то модуль устанавливает его по значению текущего времени. Поэтому каждый раз seed будет иметь другое значение, следовательно и наборы случайных чисел будут различны. Равномерно распределенные случайные числа thumb|300px|500 точек по random.uniform(-1, 1) thumb|200px|Равномерно распределенные 1000 случайных чисел thumb|200px|То же, но для миллиона случайных чисел Числа, генерируемые функцией random.random() равномерно распределяются между 0 и 1, то есть вероятность обнаружить случайное число в любой точке этого интервала одинакова. Такое распределение называется равномерным (uniform). Функция random.uniform(a, b) более общая — она генерирует равномерно распределенные случайные числа для любого интервала b). Посмотрим, правда ли они распределены равномерно: import random import matplotlib.pyplot as plt N = 500 x = range(N) y = [random.uniform(-1,1) for i in x plt.plot(x, y, 'o') plt.show() Итак, судя по картинке, это действительно равномерное распределение. Но естественно, что имеется и некоторое отклонение, поскольку мы ожидаем настоящей случайности во всем, а значит в разные участки выбранного интервала может попасть разное число точек. Давайте посмотрим, так ли это. Разделим наш интервал на несколько промежутков (пусть это будет 20) и посмотрим гистограммы распределения для разного числа точек: import random import matplotlib.pyplot as plt y = for i in xrange(10**3) plt.hist(y, 20) plt.show() Как видно из иллюстраций справа, при стремлении числа точек к бесконечности, равномерность распределения только улучшается. Random из NumPy В уже известном нам пакете Numerical Python также есть эффективный модуль построения массивов случайных чисел: from numpy import random r = random.random() # одно число между 0 и 1 r = random.random(size=10000) # массив с 10000 чисел r = random.uniform(-1, 10) # одно число между -1 и 10 r = random.uniform(-1, 10, size=10000) # массив Видно, что синтаксис этого модуля от стандартного отличается только наличием параметра size. Но построение массива для оптимизированного numpy-модуля произойдет быстрее. Среднее значение и среднеквадратическое отклонение Что такое среднее, вам, конечно известно. Может быть, вы также знакомы из теории вероятностей или статистики с такими понятиями как дисперсия (variance), среднеквадратическое отклонение (standard deviation). Все эти понятия очень важны как параметры, характеризующее распределение случайных величин и потому их вычисление уже встроено в Numerical Python: N = 10**6 from numpy import random, mean, var, std x = random.uniform(-1, 1, size=N) xm = mean(x) # среднее значение xv = var(x) # дисперсия xs = std(x) # СКО print Number: %d mean: %.2e var: %.2e stdev: %.2e % (N, xm, xv, xs) Нормальное распределение В физических приложениях и гораздо чаще встречается не равномерное, а нормальное (гауссово) распределение вокруг главного значения, вероятность обнаружить которое максимальна, а вероятность обнаружения случайных чисел вокруг него одинаково уменьшается в обе стороны. Например, такое распределение имеет рост человека, ошибки при стрельбе, неточность измерений. Распределение задается этим средним значением (m), называемым математическим ожиданием, и среднеквадратическим отклонением (s). thumb|300px|Нормальное распределение миллиона точек с помощью random.normal() Отдельные случайные числа мы можем получить либо с помощью стандартного модуля: import random as random_number r = random_number.normalvariate(m, s) либо с помощью numpy, где мы можем получить и массив чисел: from numpy import random r = random.normal(m, s, size=N) Чтобы представить вид гауссова распределения, напишем немного кода; в качестве параметров возьмем стандартное нормальное распределение — с математическим ожиданием в нуле и СКО, равным 1: from numpy import random import matplotlib.pyplot as plt N = 1E6 m = 0 s = 1 y = random.normal(m, s, size=N) plt.hist(y, 100) plt.show() Целые числа Довольно часто и не требуются распределения, а нужно выбрать из какого-то конкретного набора целых чисел b, например, при броске кубика может выпасть лишь одно из шести чисел 6, при этом с равной вероятностью. Бросить кубик может родной модуль: import random as random_number r = random_number.randint(a, b) А может и numpy. Но в отличие от встроенного, интервал здесь полуоткрытый - b) и можно задавать длину возвращаемого массива с помощью N: from numpy import random r = random.randint(a, b+1, N) Но, на самом деле, есть функция, позволяющая включать и b, то есть задавать интервал как в первом случае - [a, b: from numpy import random r = random.random_integers(a, b, N) Пример: бросание кубика Напишем программу, которая кидает кубик N раз и подсчитывает сколько раз выпала шестерка: import random as random_number N = int(raw_input('How many experiments: ')) M = 0 for i in xrange(N): outcome = random_number.randint(1, 6) if outcome 6: M += 1 print 'Got six %d times out of %d' % (M, N) Код очень понятный, разумный, в духе старого доброго программирования. Функция xrange, как мы помним, более быстрый аналог range. Но мы можем написать и элегантную «векторизованную» версию: from numpy import random, sum N = int(raw_input('How many experiments: ')) eyes = random.random_integers(1, 6, N) success = eyes 6 # True/False array M = sum(success) # True как 1, False как 0 print 'Got six %d times out of %d' % (M, N) Конструкция eyes 6 векторно сравнивает все элементы массива с 6 и возвращает True в случае совпадения. Далее True это то же, что единица, False то же, что 0 и происходит суммирование. При этом важным моментом является использование именно numpy.sum, а не стандартной функции Python, это ускоряет решение для массива до 50 раз. Случайный элемент списка Взять случайный элемент списка a очень просто: number = random_number.choice(a) Вызов random_number.choice(a) задает случайное число, number — переменная, его запоминающая. Кроме того можно перемешать элементы списка с помощью вызова функции shuffle: random_number.shuffle(a) Заметьте, что она ничего не возвращает, а преобразует сам список. Вот несколько примеров: >>> awards = 'computer', 'ball', 'pen' >>> import random as random_number >>> random_number.choice(awards) 'car' >>> awardslen(awards)-1) # заметьте, это ведь то же самое 'pen' >>> random_number.shuffle(awards) >>> awards0 'computer' Пример: Колода карт Следующая функция создает и тасует колоду карт, где каждая карта представлена строкой, а колода представляет список таких строк: def make_deck(): ranks = '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K' suits = 'D', 'H', 'S' deck = [] for s in suits: for r in ranks: deck.append(s + r) random_number.shuffle(deck) return deck Здесь ’A’ означает туз (ace), ’J’ соответствует валету (jack), ’Q’ — королеве (queen), ’K’ — королю (king); ’C’ это трефы (clubs), ’D’ — бубны (diamonds), ’H’ — черви (hearts) и ’S’ — пики (spades). Достать колоду из карты можно так: deck = make_deck() card = deck.pop(0) # возвратить и удалить элемент с индексом 0 (="вынуть") Раздать несколько карт можно так: def deal_hand(n, deck): hand = [decki for i in range(n)] del deck:n return hand, deck Заметьте, что мы должны передать функции изменяемый список deck. Следующая функция раздает карты нескольким игрокам: def deal(cards_per_hand, no_of_players): deck = make_deck() hands = [] for i in range(no_of_players): hand, deck = deal_hand(cards_per_hand, deck) hands.append(hand) return hands players = deal(5, 4) import pprint; pprint.pprint(players) ['H6', 'H4', 'CJ', 'DA', 'SJ', 'HA', 'C9', 'H3', 'D3', 'CA', 'H7', 'S7', 'S10', 'S2', 'H9', 'C3'] Теперь смотрим что у нас на руках. Интересным для нас будет наличие пары или тройки карт с одинаковым значением и сколько имеется карт одинаковой масти. Для этого две функции: def same_rank(hand, n_of_a_kind): ranks = [card1: for card in hand] counter = 0 already_counted = [] for rank in ranks: if rank not in already_counted and ranks.count(rank) n_of_a_kind: counter += 1 already_counted.append(rank) return counter def same_suit(hand): suits = [card0 for card in hand] counter = {} for suit in suits: count = suits.count(suit) if count > 1: countersuit = count return counter Теперь посмотрим что игроков на руках после первой раздачи: for hand in players: print The hand %s has %d pairs, %s 3-of-a-kind and %s cards of the same suit. % \ (', '.join(hand), same_rank(hand, 2), same_rank(hand, 3),\ '+'.join(for s in same_suit(hand).values())) The hand DQ, H6, H4, CJ, DA has 0 pairs, 0 3-of-a-kind and 2+2 cards of the same suit. The hand C2, SJ, HA, C9, H3 has 0 pairs, 0 3-of-a-kind and 2+2 cards of the same suit. The hand S6, D3, CA, H7, S7 has 1 pairs, 0 3-of-a-kind and 2 cards of the same suit. The hand C7, S10, S2, H9, C3 has 0 pairs, 0 3-of-a-kind and 2+2 cards of the same suit. Продолжение примера: Класс На двух предыдущих уроках мы познакомились с классами и их специальными методами. Этот пример отлично годится для того, чтобы применить к нему наши знания. Можно заметить, что deck мы перемещаем из одной функции в другую и эти функции представляются отличными кандидатами на методы класса: class Deck: def __init__(self): ranks = '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K' suits = 'D', 'H', 'S' self.deck = for s in suits for r in ranks random_number.shuffle(self.deck) def hand(self, n=1): """Deal n cards. Return hand as list.""" hand = [self.decki for i in range(n)] # pick cards del self.deck:n # remove cards return hand def deal(self, cards_per_hand, no_of_players): """Deal no_of_players hands. Return list of lists.""" return \ for i in range(no_of_players) def putback(self, card): """Put back a card under the rest.""" self.deck.append(card) def __str__(self): return str(self.deck) Далее мы можем этим пользоваться этим классом, например из файла, куда мы его поместили Deck.py: from Deck import Deck deck = Deck() print deck players = deck.deal(5, 4) Ссылки *Документация по модулю random (англ.) *Документация по numpy.random (англ.) Категория:Программирование и научные вычисления на языке Python